package com.dingyang.learn.handmadespringframework.springframework;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * 上下文
 * @author dingyang
 */
public class DingYangApplicationContext {

    private Class<?> configClass;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    /**
     * 一级缓存
     */
    private Map<String, Object> singletonObjectMap = new HashMap<>();

    /**
     * 二级缓存
     */
    private Map<String, Object> earlySingletonObjectMap = new HashMap<>();

    /**
     * 三级缓存
     */
    private Map<String, ObjectFactory> objectFactoryMap = new HashMap<>();

    private Map<String, BeanPostProcessor> beanPostProcessorMap = new HashMap<>();

    public DingYangApplicationContext(Class<?> configClass) throws Exception {
        this.configClass = configClass;
        // 扫描
        this.scan();
        // 注册BeanPostProcessor
        registryBeanPostProcessor();
        // 实例化单例
        preInstantiateSingletons();
    }

    /**
     * 扫描
     * @throws ClassNotFoundException
     */
    private void scan() throws ClassNotFoundException, URISyntaxException {
        String scanPackage = null;
        if (this.configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = this.configClass.getAnnotation(ComponentScan.class);
            scanPackage = componentScan.value();
        }
        if (scanPackage == null || scanPackage.isEmpty()) {
             scanPackage = this.configClass.getPackageName();
        }
        String scanPath = scanPackage.replace(".", File.separator);
        ClassLoader configClassLoader = this.configClass.getClassLoader();
        URL resource = configClassLoader.getResource(scanPath);
        // resource.getFile()会出现路径编解码问题导致file无法正确解析
        // File file = new File(resource.getFile());
        File file = new File(resource.toURI());
        String basePath = file.getAbsolutePath().substring(0, file.getAbsolutePath().indexOf(scanPath));
        readBeanDefinition(file, basePath);

        // (Spring: 解析@Import  @Bean)  ...
        // (Spring: @EnableAspectJAutoProxy--> 导入AutoProxyCreatorBeanPostProcessor)
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setBeanClass(AutoProxyCreatorBeanPostProcessor.class);
        beanDefinitionMap.put("autoProxyCreatorBeanPostProcessor", beanDefinition);
    }

    private void registryBeanPostProcessor() throws Exception {
        for (String beanName : beanDefinitionMap.keySet()) {
            if (BeanPostProcessor.class.isAssignableFrom(beanDefinitionMap.get(beanName).getBeanClass())) {
                BeanPostProcessor bean = (BeanPostProcessor) getBean(beanName);
                if (bean != null) {
                    beanPostProcessorMap.put(beanName, bean);
                }
            }
        }
    }

    /**
     * 读取BeanDefinination
     * @param file
     * @param basePath
     * @throws ClassNotFoundException
     */
    private void readBeanDefinition(File file, String basePath) throws ClassNotFoundException {
        if (file.isFile()) {
            String classPath = file.getAbsolutePath().replace(basePath, "");
            String className = classPath.replace(File.separator, ".").replace(".class", "");
            Class<?> beanClass = this.configClass.getClassLoader().loadClass(className);
            if (beanClass.isAnnotationPresent(Component.class)) {
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setBeanClass(beanClass);
                if (beanClass.isAnnotationPresent(Lazy.class)) {
                    beanDefinition.setLazy(true);
                }
                if (beanClass.isAnnotationPresent(Scope.class)) {
                    Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
                    beanDefinition.setScope(scopeAnnotation.value());
                }
                Component componentAnnotation = beanClass.getAnnotation(Component.class);
                String beanName = componentAnnotation.value();
                if (beanName.isEmpty()) {
                    beanName = Introspector.decapitalize(beanClass.getSimpleName());
                }
                this.beanDefinitionMap.put(beanName, beanDefinition);
            }
        } else {
            File[] fileArr = file.listFiles();
            for (File subFile : fileArr) {
                readBeanDefinition(subFile, basePath);
            }
        }
    }

    private void preInstantiateSingletons() throws Exception {
        for (String beanName : this.beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
            if (!beanDefinition.isSingleton() || beanDefinition.isLazy()) {
                continue;
            }
            getBean(beanName);
        }
    }

    public Object getBean(String beanName) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new RuntimeException("不存在bean[" + beanName + "]");
        }
        Object bean = getSingleton(beanName);
        if (bean != null) {
            return bean;
        }
        synchronized (singletonObjectMap) {
            bean = getSingleton(beanName);
            if (bean != null) {
                return bean;
            }
            // 实例化
            Object beanInstance = beanDefinition.getBeanClass().getConstructor().newInstance();
            final Object finalBeanInstance = beanInstance;
            objectFactoryMap.put(beanName, (ObjectFactory<Object>) () -> {
                for (String beanPostProcessorName : beanPostProcessorMap.keySet()) {
                    // 多个呢
                    if (beanPostProcessorMap.get(beanPostProcessorName) instanceof SmartInstantiationAwareBeanPostProcessor) {
                        return ((SmartInstantiationAwareBeanPostProcessor) beanPostProcessorMap.get(beanPostProcessorName)).getEarlyBeanReference(finalBeanInstance, beanName);
                    }
                }
                return finalBeanInstance;
            });
            // // 加入二级缓存
            // earlySingletonObjectMap.put(beanName, beanInstance);
            // 属性注入
            Field[] declaredFields = beanDefinition.getBeanClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    Autowired autowiredAnnotation = declaredField.getAnnotation(Autowired.class);
                    String autowiredBeanName = autowiredAnnotation.value();
                    if (autowiredBeanName.isEmpty()) {
                        autowiredBeanName = declaredField.getName();
                    }
                    Object autowiredBean = getBean(autowiredBeanName);
                    declaredField.setAccessible(true);
                    declaredField.set(beanInstance, autowiredBean);
                }
            }
            // 初始化前
            for (String beanPostProcessorName : beanPostProcessorMap.keySet()) {
                beanInstance = beanPostProcessorMap.get(beanPostProcessorName).postProcessBeforeInitialization(beanInstance, beanName);
            }
            // 初始化
            if (beanInstance instanceof InitializingBean) {
                ((InitializingBean) beanInstance).afterPropertiesSet();
            }
            // 初始化后
            for (String beanPostProcessorName : beanPostProcessorMap.keySet()) {
                beanInstance = beanPostProcessorMap.get(beanPostProcessorName).postProcessAfterInitialization(beanInstance, beanName);
            }
            // 单例bean
            singletonObjectMap.put(beanName, beanInstance);
            // 移除二级缓存
            earlySingletonObjectMap.remove(beanName);
            return beanInstance;
        }
    }

    private Object getSingleton(String beanName) {
        if (singletonObjectMap.containsKey(beanName)) {
            return singletonObjectMap.get(beanName);
        }
        synchronized (singletonObjectMap) {
            if (earlySingletonObjectMap.containsKey(beanName)) {
                return earlySingletonObjectMap.get(beanName);
            }
            if (objectFactoryMap.containsKey(beanName)) {
                Object bean = objectFactoryMap.get(beanName).getObject();
                earlySingletonObjectMap.put(beanName, bean);
                objectFactoryMap.remove(beanName);
                return bean;
            }
        }
        return null;
    }

}
